package rdbms

import (
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"io/ioutil"
	"time"

	"github.com/go-sql-driver/mysql"

	"github.com/golang/glog"
)

// MysqlConnectParams database init params.
type MysqlConnectParams struct {
	DBName         string
	User           string
	Password       string
	Addr           string
	Collation      string
	Charset        string
	Local          string
	ParseTime      bool
	ConnectTimeout string
	ReadTimeout    string
	WriteTimeout   string
	SSLMode        string
	SSLCert        string
	SSLKey         string
	SSLRootCert    string
}

type mysqlConnectManager struct{}

// NewMysqlConnectManager new a database manager
func NewMysqlConnectManager() ConnectManager {
	return new(mysqlConnectManager)
}

func (inst *mysqlConnectManager) Init(env *Env) (*sql.DB, error) {
	params, ok := env.ConnectParams.(MysqlConnectParams)
	if !ok {
		glog.Fatalln("database connect parameters not set or type incorrect")
	}
	connStr := params.User
	if params.Password != "" {
		connStr += ":" + params.Password
	}
	connStr += "@tcp"
	if params.Addr != "" {
		connStr += "(" + params.Addr + ")"
	} else {
		connStr += "(127.0.0.1:3306)"
	}
	connStr += "/" + params.DBName
	if params.Collation != "" {
		connStr += "?collation=" + params.Collation
	} else {
		connStr += "?collation=utf8mb4_general_ci"
	}
	if params.Charset != "" {
		connStr += "&charset=" + params.Charset
	} else {
		connStr += "&charset=utf8mb4"
	}
	if params.Local != "" {
		connStr += "&loc=" + params.Local
	} else {
		connStr += "&loc=UTC"
	}
	if params.ParseTime != true {
		connStr += "&parseTime=false"
	} else {
		connStr += "&parseTime=true"
	}
	if params.ConnectTimeout != "" {
		connStr += "&timeout=" + params.ConnectTimeout
	}
	if params.ReadTimeout != "" {
		connStr += "&readTimeout=" + params.ReadTimeout
	}
	if params.WriteTimeout != "" {
		connStr += "&writeTimeout=" + params.WriteTimeout
	}
	if params.SSLMode == "true" || params.SSLMode == "false" || params.SSLMode == "skip-verify" {
		connStr += "&tls=" + params.SSLMode
	} else {
		connStr += "&tls=verify-full"
		rootCertPool := x509.NewCertPool()
		pem, err := ioutil.ReadFile(params.SSLRootCert)
		if err != nil {
			glog.Fatalln(err)
		}
		if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
			glog.Fatalln("Failed to append PEM.")
		}
		clientCert := make([]tls.Certificate, 0, 1)
		certs, err := tls.LoadX509KeyPair(params.SSLCert, params.SSLKey)
		if err != nil {
			glog.Fatalln(err)
		}
		clientCert = append(clientCert, certs)
		mysql.RegisterTLSConfig("verify-full", &tls.Config{
			RootCAs:      rootCertPool,
			Certificates: clientCert,
		})
	}
	conn, err := sql.Open("mysql", connStr)
	if err != nil {
		glog.Fatalln(err)
	}
	if err = conn.Ping(); err != nil {
		glog.Fatalln(err)
	}
	if env.MaxIdle == 0 {
		conn.SetMaxIdleConns(2)
	}
	conn.SetMaxIdleConns(env.MaxIdle)
	if env.MaxOpen == 0 {
		conn.SetMaxOpenConns(10)
	}
	conn.SetMaxOpenConns(env.MaxOpen)
	if env.ConnMaxLifetime == 0 {
		conn.SetConnMaxLifetime(3 * time.Second)
	}
	conn.SetConnMaxLifetime(env.ConnMaxLifetime * time.Second)
	return conn, err
}

func (inst *mysqlConnectManager) Destroy(conn *sql.DB) error {
	err := conn.Close()
	return err
}
